#ifndef __ZUTF8CHAR_HPP
#define __ZUTF8CHAR_HPP

#include "../zui.h"

using namespace std;

class ZUtf8Char
{
	private:char*	data;
	private:uint8_t	msize;//memory size
	private:uint8_t	width;
	
	public:ZUtf8Char(void): 
	data(nullptr), msize(0), width(0) {}
	
	public:ZUtf8Char(char* charAt): data(nullptr)
	{
		//00000000:0
		//10xxxxxx:128
		//110xxxxx:192
		//1110xxxx:224
		//11110xxx:240
		//11111000:248
		
		if((*charAt & 128) == 0)
		{
			msize = 1;
			width = 1;
			data = new char;
			*data = *charAt;
		}
		else if((*charAt & 224) == 192)
		{
			msize = 2;
			width = 1;
			data = new char[2];
			*data = *(charAt++);
			*(data+1) = *charAt;
		}
		else if((*charAt & 240) == 224)
		{
			msize = 3;
			width = 2;
			data = new char[3];
			*data = *(charAt++);
			*(data+1) = *(charAt++);
			*(data+2) = *charAt;
		}
		else if((*charAt & 248) == 240)
		{
			msize = 4;
			width = 2;
			data = new char[4];
			*data = *(charAt++);
			*(data+1) = *(charAt++);
			*(data+2) = *(charAt++);
			*(data+3) = *charAt;
		}
	}
	
	public:ZUtf8Char(char** readpp): data(nullptr)
	{
		if((**readpp & 128) == 0)
		{
			msize = 1;
			width = 1;
			data = new char;
			*data = *((*readpp)++);
		}
		else if((**readpp & 224) == 192)
		{
			msize = 2;
			width = 1;
			data = new char[2];
			*data = *((*readpp)++);
			*(data+1) = *((*readpp)++);
		}
		else if((**readpp & 240) == 224)
		{
			msize = 3;
			width = 2;
			data = new char[3];
			*data = *((*readpp)++);
			*(data+1) = *((*readpp)++);
			*(data+2) = *((*readpp)++);
		}
		else if((**readpp & 248) == 240)
		{
			msize = 4;
			width = 2;
			data = new char[4];
			*data = *((*readpp)++);
			*(data+1) = *((*readpp)++);
			*(data+2) = *((*readpp)++);
			*(data+3) = *((*readpp)++);
		}
	}
	
	public:ZUtf8Char(const char& c): data(nullptr)
	{	//dangerous
		operator = (c);
	}
	
	public:ZUtf8Char(const ZUtf8Char& another): data(nullptr)
	{
		operator = (another);
	}
	
	public:ZUtf8Char(ZUtf8Char&& another) noexcept: data(nullptr)
	{
		operator = (another);
	}
	
	private:void disable(void) {if(data != nullptr) delete data;}
	public:~ZUtf8Char(void) {disable();}
	
	public:ZUtf8Char& operator = (const ZUtf8Char& another)
	{
		msize = another.msize;
		width = another.width;
		disable();
		data = new char[msize];
		memcpy(data, another.data, msize);
		return *this;
	}
	
	public:ZUtf8Char& operator = (ZUtf8Char&& another) noexcept
	{
		msize = another.msize;
		width = another.width;
		disable();
		data = another.data;
		another.data = nullptr;
		return *this;
	}
	
	public:ZUtf8Char& operator = (const char& c)
	{	//dangerous
		const char* charAt = &c;
		if((c & 128) == 0)
		{
			msize = 1;
			width = 1;
			data = new char;
			data[0] = c;
		}
		else if((c & 224) == 192)
		{
			msize = 2;
			width = 1;
			data = new char[2];
			data[0] = *(charAt++);
			data[1] = *charAt;
		}
		else if((c & 240) == 224)
		{
			msize = 3;
			width = 2;
			data = new char[3];
			data[0] = *(charAt++);
			data[1] = *(charAt++);
			data[2] = *charAt;
		}
		else if((c & 248) == 240)
		{
			msize = 4;
			width = 2;
			data = new char[4];
			data[0] = *(charAt++);
			data[1] = *(charAt++);
			data[2] = *(charAt++);
			data[3] = *charAt;
		}
		return *this;
	}
	
	public:static uint8_t getCharMemorySize(char c)
	{
		if((c & 128) == 0)			return 1;
		else if((c & 224) == 192)	return 2;
		else if((c & 240) == 224)	return 3;
		else if((c & 248) == 240)	return 4;
		else						return 0;
	}
	
	public:uint8_t getMemorySize(void) const
	{
		return msize;
	}
	
	public:uint8_t getWidth(void) const
	{
		if(data == nullptr) return 0;
		return width;
	}
	
	
	public:uint8_t getWidth(size_t startIndex)
	{
		if(data == nullptr) return 0;
		if(data[0] == '\t')
		{
			return width = uint8_t (8 - (startIndex % 8));
		}
		else return width;
	}
	
	public:void updateTabCharWidth(uint8_t w)
	{
		if(data != nullptr  &&  data[0] == '\t') width = w;
	}
	
	public:ZUtf8Char& get(int (*getFunc)(void))
	{
		disable();
		char firstByte;
		firstByte = getFunc();
		
		if((firstByte & 248) == 240)
		{
			msize = 4;
			width = 2;
			
			data = new char[4];
			data[0] = firstByte;
			data[1] = getFunc();
			data[2] = getFunc();
			data[3] = getFunc();
			
		}
		else if((firstByte & 240) == 224)
		{
			msize = 3;
			width = 2;
			
			data = new char[3];
			data[0] = firstByte;
			data[1] = getFunc();
			data[2] = getFunc();
		}
		else if((firstByte & 224) == 192)
		{
			msize = 2;
			width = 1;
			
			data = new char[2];
			data[0] = firstByte;
			data[1] = getFunc();
		}
		else if((firstByte & 128) == 0)
		{
			msize = 1;
			width = 1;
			data = new char;
			data[0] = firstByte;
		}
		
		return *this;
	}
	
	public:friend istream& operator >> (istream& ist, ZUtf8Char& c)
	{
		c.disable();
		char firstByte;
		ist >> firstByte;
		
		if((firstByte & 248) == 240)
		{
			c.msize = 4;
			c.width = 2;
			
			c.data = new char[4];
			c.data[0] = firstByte;
			ist >> c.data[1] >> c.data[2] >> c.data[3];
			
		}
		else if((firstByte & 240) == 224)
		{
			c.msize = 3;
			c.width = 2;
			
			c.data = new char[3];
			c.data[0] = firstByte;
			ist >> c.data[1] >> c.data[2];
		}
		else if((firstByte & 224) == 192)
		{
			c.msize = 2;
			c.width = 1;
			
			c.data = new char[2];
			c.data[0] = firstByte;
			ist >> c.data[1];
		}
		else if((firstByte & 128) == 0)
		{
			c.msize = 1;
			c.width = 1;
			c.data = new char;
			c.data[0] = firstByte;
		}
		
		return ist;
	}
	
	public:friend ostream& operator << (ostream& ost, const ZUtf8Char& c)
	{
		for(int i = 0; i < c.msize; ++i)
		{
			ost << c.data[i];
		}
		return ost;
	}
	
	public:operator const char*(void) const
	{
		return data;
	}
	
	public:operator int(void) const
	{
		return getUtf8();
	}
	
	public:static int getUtf8(const char& c)
	{
		uint8_t msize;
		int utf8 = 0;
		memcpy(reinterpret_cast<char*>(&utf8), &c, msize=getCharMemorySize(c));
		switch(msize)
		{
			case 1: utf8 >>= 8;
			case 2: utf8 >>= 8;
			case 3: utf8 >>= 8;
		}
		return utf8;
	}
	
	public:int getUtf8(void) const
	{
		int utf8 = 0;
		memcpy(reinterpret_cast<char*>(&utf8), data, msize);
		switch(msize)
		{
			case 1: utf8 >>= 8;
			case 2: utf8 >>= 8;
			case 3: utf8 >>= 8;
		}
		return utf8;
	}
	
	public:bool operator == (const ZUtf8Char& another) const
	{
		if(msize != another.msize) return false;
		return strncmp(data, another.data, msize) == 0;
	}
	
	public:bool operator == (const char& c) const
	{
		uint8_t csize = getCharMemorySize(c);
		if(csize != msize) return false;
		return strncmp(data, &c, csize) == 0;
	}
	
	public:bool operator != (const ZUtf8Char& another) const
	{
		if(msize != another.msize) return true;
		return strncmp(data, another.data, msize) != 0;
	}
	public:bool operator != (const char& c) const
	{
		uint8_t csize = getCharMemorySize(c);
		if(csize != msize) return true;
		return strncmp(data, &c, csize) != 0;
	}
	
	public:bool operator > (const ZUtf8Char& another) const
	{
		return getUtf8() > another.getUtf8();
	}
	public:bool operator > (const char& c) const
	{
		return getUtf8() > getUtf8(c);
	}
	
	public:bool operator < (const ZUtf8Char& another) const
	{
		return getUtf8() < another.getUtf8();
	}
	public:bool operator < (const char& c) const
	{
		return getUtf8() < getUtf8(c);
	}
	
	public:bool operator >= (const ZUtf8Char& another) const
	{
		return getUtf8() >= another.getUtf8();
	}
	public:bool operator >= (const char& c) const
	{
		return getUtf8() >= getUtf8(c);
	}
	
	public:bool operator <= (const ZUtf8Char& another) const
	{
		return getUtf8() <= another.getUtf8();
	}
	public:bool operator <= (const char& c) const
	{
		return getUtf8() <= getUtf8(c);
	}
};

#endif